package com.appspot.yusupova.verses;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jdo.PersistenceManager;

/**
 * Карта хранения и поиска стихов и их индексов в хранилище JDO.
 *  
 * Решает следующие задачи:
 * <ol>
 * <li>сохранение нового стиха в хранилище </li>
 * <li>сохранение нового индекса стихов в хранилище</li>
 * <li>поиск стиха по заданной строке ключевых слов и возврат наиболее подходящего</li> 
 * <li>возврат всех стихов с их идентификаторами и текстом</li>
 * </ol>
 * 
 * @author Rouslan Shchoutchinov
 */
public final class VerseMap {

    // константа "0", позволяющая не выделять каждый раз память для 0-значения
    private static final Integer ZERO = new Integer(0);
    
    /**
     * Возвращает все стихи, которые хранятся в хранилище JDO
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    public static String getAll() {
        PersistenceManager manager = PMF.get().getPersistenceManager();
        List<Verse> verses = (List<Verse>) manager.newQuery(Verse.class).execute();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < verses.size(); i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            Verse verse = verses.get(i);
            buffer.append("[id=");
            buffer.append(verse.getId());
            buffer.append(",text=");
            buffer.append(verse.getText());
            buffer.append("]");
        }
        manager.close();
        return buffer.toString();
    }
    
    /**
     * Возвращает все индексы стихов, которые хранятся в хранилище JDO
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    public static String getAllIndexes() {
        PersistenceManager manager = PMF.get().getPersistenceManager();
        List<VerseIndex> indexes = (List<VerseIndex>) manager.newQuery(VerseIndex.class).execute();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < indexes.size(); i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            VerseIndex index = indexes.get(i);
            buffer.append("[word=");
            buffer.append(index.getWord());
            buffer.append(",ids=");
            StringBuilder buffer2 = new StringBuilder();
            for (Long verseId : index.getVerseIds()) {
                if (buffer2.length() > 0) {
                    buffer2.append(",");
                }
                buffer2.append(verseId);
            }
            buffer.append(buffer2);
            buffer.append("]");
        }
        manager.close();
        return buffer.toString();
    }
    
    /**
     * Возвращает стих, содержащий слова из заданного текста  
     * 
     * @param text текст для поиска стиха
     * @return
     */
    public static String get(String text) {
        Set<String> words = getWords(text, 10);
        PersistenceManager manager = PMF.get().getPersistenceManager();
        try {
            List<VerseIndex> verseIndexes = getVerseIndexes(manager, words);
            Map<Long, Integer> verseIdWeightMap = getVerseIdWeightMap(verseIndexes);
            Long id = getFinalVerseId(verseIdWeightMap);
            if (id != null) {
                Verse verse = manager.getObjectById(Verse.class, id);
                return verse.getText();
            }
        } finally {
            manager.close();
        }
        return null;
    }
    
    /**
     * Размещает новый стих в хранилище JDO.
     * 
     * @param text - текст нового стиха
     */
    public static Long put(String text) {
        Verse verse = new Verse();
        verse.setText(text);
        PersistenceManager manager = PMF.get().getPersistenceManager();
        try {
            manager.makePersistent(verse);
        } finally {
            manager.close();
        }
        return verse.getId();
    }
    
    /**
     * Размещает индекс в хранилище JDO.
     * 
     * @param word     слово, которое встречается в стихах
     * @param verseIds идентификаторы стихов, в которых встречается слово 
     */
    public static void putIndex(String word, List<Long> verseIds) {
        VerseIndex verseIndex = new VerseIndex();
        verseIndex.setWord(word);
        verseIndex.setVerseIds(verseIds);
        PersistenceManager manager = PMF.get().getPersistenceManager();
        try {
            manager.makePersistent(verseIndex);
        } finally {
            manager.close();
        }
    }
    
    // возвращает подмножество всех слов, где могло встретиться заданное слово
    private static Set<String> getWords(String text, int wordLimit) {
        Set<String> words = new HashSet<String>();
        int len = text.length();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i <= len; i++) {
            char ch = i < len ? text.charAt(i) : ' ';
            if (Character.isDigit(ch) || Character.isLetter(ch)) {
                buffer.append(Character.toLowerCase(ch));
            } else {
                String word = buffer.toString();
                buffer.setLength(0);
                if (word.length() > 0) {
                    words.add(word);
                    if (words.size() >= wordLimit) {
                        break;
                    }
                }
            }
        }
        Set<String> result = new HashSet<String>();
        for (String word : words) {
            int minSubwordLen = getMinSubwordLen(word.length());
            for (int subwordLen = word.length(); subwordLen >= minSubwordLen; subwordLen--) {
                result.add(word.substring(0, subwordLen));
            }
        }
        return result;
    }
    
    // возвращает минимальный размер подстроки для поиска
    private static int getMinSubwordLen(int wordLen) {
        if (wordLen <= 3) {
            return wordLen;
        } 
        if (wordLen == 4) {
            return wordLen - 1;
        } 
        return wordLen - 2;
    }
    
    // возвращает индексы всех стихов, в которых встречаются заданные слова 
    @SuppressWarnings("unchecked")
    private static List<VerseIndex> getVerseIndexes(PersistenceManager manager, Set<String> words) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("select from ");
        buffer.append(VerseIndex.class.getName());
        boolean firstParam = true;
        for (String word : words) {
            if (firstParam) {
                firstParam = false;
                buffer.append(" where ");
            } else {
                buffer.append(" || ");
            }
            buffer.append("word == '");
            buffer.append(word);
            buffer.append("'");
        }
        return (List<VerseIndex>) manager.newQuery(buffer.toString()).execute();
    }
    
    // возвращает карту "идентификатор стиха - вес стиха"
    private static Map<Long, Integer> getVerseIdWeightMap(List<VerseIndex> verseIndexes) {
        Map<Long, Integer> result = new HashMap<Long, Integer>();
        for (VerseIndex verseIndex : verseIndexes) {
            int additionalWeight = verseIndex.getWord().length();  
            for (Long verseId : verseIndex.getVerseIds()) {
                Integer weight = result.get(verseId);
                if (weight == null) {
                    weight = ZERO;
                }
                result.put(verseId, weight + additionalWeight);
            }
        }
        return result;
    }
    
    // возвращает идентификатор стиха с самым большим весом
    private static Long getFinalVerseId(Map<Long, Integer> verseIdWeightMap) {
        Long bestVerseId = null;
        int bestWeight = 0;
        for (Long verseId : verseIdWeightMap.keySet()) {
            int weight = verseIdWeightMap.get(verseId);
            if (bestVerseId == null || weight > bestWeight) {
                bestVerseId = verseId;
                bestWeight = weight;
            }
        }
        return bestVerseId;
    }

}
